This study allows us to revisit/renew
- Regression modeling
- Properties of Least Squares/Fitting “a line”
- Multiple observation
Datasets for this study are
- The main file: gauge.txt
- Supplementary large-scale files: download the following folder Full Resolution Data.zip More information about the supplementary file can be found at http://iabp.apl.washington.edu/data.html as well as http://nsidc.org/data/G00791
Question
The aim of this lab is to provide a simple procedure for converting gain into density when the gauge is in operation. Keep in mind that the experiment was conducted by varying density and measuring the response in gain, but when the gauge is ultimately in use, the snow-pack density is to be estimated from the measured gain.
Setup
df <- read.table('gauge-1wb1wa6-2gpel41.txt', header=TRUE)
df <- df[order(df$density), ] # Sort from least to greatest density
m <- 9 # Number of distinct block densities
t <- 10 # Number of replicate measurements
#install.packages('L1pack')
#install.packages('quantreg')
#install.packages('ggplot2')
library(L1pack) # Used for least absolute deviations regression line
library(quantreg) # Used for quantile regression line
library(ggplot2)
Scenario 1: Fitting
Use the data to fit the gain, or a transformation of gain, to density. Try sketching the least squares line on a scatter plot.
- Do the residuals indicate any problems with the fit?
- If the densities of the polyethylene blocks are not reported exactly, how might this affect the fit?
- What if the blocks of polyethylene were not measured in random order (location)?
# Plot raw data
title <- 'Density vs. Gain'
x.axis <- expression('Density (g/cm'^3*')')
y.axis <- 'Gain'
x.range <- c(0, .7)
y.range <- c(2.5, 6)
plot(df, main=title, xlab=x.axis, ylab=y.axis, xlim=x.range)

# Take log transformation of response variable (gain)
y.log.axis = 'log(Gain)'
df.log = data.frame(df['density'], log(df['gain']))
plot(df.log, main=title, xlab=x.axis, ylab=y.log.axis, xlim=x.range, ylim=y.range)

# Average replicate measurements
df.log.avg = aggregate(list(gain=df.log$gain), by=list(density=df.log$density), FUN=mean)
plot(df.log.avg, main=title, xlab=x.axis, ylab=y.log.axis, xlim=x.range, ylim=y.range)

# Fit gain to density
least.squares <- lm(gain~density, data=df.log.avg)
lad <- lad(gain~density, data=df.log.avg)
quant <- rq(gain~density, tau=.5, data=df.log.avg)
plot(df.log, main=title, xlab=x.axis, ylab=y.log.axis, xlim=x.range, ylim=y.range)
abline(least.squares, col='red')
abline(lad, col='blue')
abline(quant, col='green')
legend('topright', legend=c('Least Squares Regression Line', 'Least Absolute Deviations Regression Line', '50% Quantile Regression Line'), col=c('red', 'blue', 'green'), lty=1)

c(cor(df.log.avg), summary(least.squares)$r.squared)
[1] 1.0000000 -0.9984469 -0.9984469 1.0000000 0.9968963
least.squares
Call:
lm(formula = gain ~ density, data = df.log.avg)
Coefficients:
(Intercept) density
5.997 -4.606
lad
Call:
lad(formula = gain ~ density, data = df.log.avg)
Converged in 4 iterations
Coefficients:
(Intercept) density
5.9850 -4.5935
Degrees of freedom: 9 total; 7 residual
Scale estimate: 0.06926379
quant
Call:
rq(formula = gain ~ density, tau = 0.5, data = df.log.avg)
Coefficients:
(Intercept) density
5.985029 -4.593460
Degrees of freedom: 9 total; 7 residual
# Check conditions for linear regression: linearity, normality of residuals, and constant variability
least.squares.residuals <- data.frame(df.log['density'], df.log['gain'] - rep(predict(least.squares), each=10))
lad.residuals <- data.frame(df.log['density'], df.log['gain'] - rep(predict(lad), each=10))
quant.residuals <- data.frame(df.log['density'], df.log['gain'] - rep(predict(quant), each=10))
title.residuals1 <- 'Residuals of Least Squares Regression Line'
title.residuals2 <- 'Residuals of Least Absolute Deviations Regression Line'
title.residuals3 <- 'Residuals of 50% Quantile Regression Line'
plot(least.squares.residuals$gain, main=title.residuals1, ylab=y.axis)
abline(0, 0, col='red')

plot(lad.residuals$gain, main=title.residuals2, ylab=y.axis)
abline(0, 0, col='blue')

plot(quant.residuals$gain, main=title.residuals3, ylab=y.axis)
abline(0, 0, col='green')

num.bins <- 12
hist(least.squares.residuals$gain, breaks=num.bins, main=title.residuals1, xlab=y.axis, col='red')

hist(lad.residuals$gain, breaks=num.bins, main=title.residuals2, xlab=y.axis, col='blue')

hist(quant.residuals$gain, breaks=num.bins, main=title.residuals3, xlab=y.axis, col='green')

qqnorm(least.squares.residuals$gain, main=paste('Normal Q-Q Plot with', title.residuals1), cex.main=1)
qqline(least.squares.residuals$gain, col='red')

qqnorm(lad.residuals$gain, main=paste('Normal Q-Q Plot with', title.residuals2), cex.main=1)
qqline(lad.residuals$gain, col='blue')

qqnorm(quant.residuals$gain, main=paste('Normal Q-Q Plot with', title.residuals3), cex.main=1)
qqline(quant.residuals$gain, col='green')

Scenario 2: Predicting
Ultimately we are interested in answering questions such as: Given a gain reading of 38.6, what is the density of the snow-pack? Given a gain reading of 426.7, what is the density of the snow-pack? These two numeric values, 38.6 and 426.7, were chosen because they are the average gains for the 0.508 and 0.001 densities, respectively.
- Develop a procedure for adding bands around your least squares line that can be used to make interval estimates for the snow-pack density from gain measurements. Keep in mind how the data were collected: several measurements of gain were taken for polyenythylene blocks of known density.
# Predictions
PredictLogGain <- function(density)
predict(least.squares, data.frame(density=density)) # Predict log(gain) using density
PredictDensityLeastSquares <- function(gain) {
intercept <- coef(least.squares)[[1]]
slope <- coef(least.squares)[[2]]
(log(gain) - intercept) / slope # Predict density using gain
}
PredictDensityLad <- function(gain) {
intercept <- coef(lad)[[1]]
slope <- coef(lad)[[2]]
(log(gain) - intercept) / slope # Predict density using gain
}
PredictDensityQuant <- function(gain) {
intercept <- coef(quant)[[1]]
slope <- coef(quant)[[2]]
(log(gain) - intercept) / slope # Predict density using gain
}
# 95% prediction and confidence intervals of log(gain) using density
t <- qt(.975, df=m-2)
mean.density <- mean(df.log.avg$density)
summation <- sum((df.log.avg$density - mean.density) ^ 2)
s2 <- aggregate(list(variance=least.squares.residuals$gain), by=list(density=least.squares.residuals$density), FUN=var)
s.pooled <- sqrt(mean(s2$variance))
center.expr <- quote(center <- PredictLogGain(density))
ci.width.expr <- quote(width <- t * s.pooled * sqrt(1/m + (density-mean.density)^2 / summation))
pi.width.expr <- quote(width <- t * s.pooled * sqrt(1 + 1/m + (density-mean.density)^2 / summation))
LogGainCiLower <- function(density) {
eval(center.expr)
eval(ci.width.expr)
center - width
}
LogGainCiUpper <- function(density) {
eval(center.expr)
eval(ci.width.expr)
center + width
}
LogGainPiLower <- function(density) {
eval(center.expr)
eval(pi.width.expr)
center - width
}
LogGainPiUpper <- function(density) {
eval(center.expr)
eval(pi.width.expr)
center + width
}
# Add bands around least squares line
plot(df.log, main=title, xlab=x.axis, ylab=y.log.axis, xlim=x.range, ylim=y.range)
abline(least.squares, col='red')
ci.col <- 'purple'
pi.col <- 'blue'
symbol <- '-'
size <- 1.5
line.type <- 3
line.width <- 0.7
confidence.intervals <- data.frame(density=df.log.avg$density, lower=LogGainCiLower(df.log.avg$density), upper=LogGainCiUpper(df.log.avg$density))
points(x=confidence.intervals$density, y=confidence.intervals$lower, col=ci.col, pch=symbol, cex=size)
points(x=confidence.intervals$density, y=confidence.intervals$upper, col=ci.col, pch=symbol, cex=size)
lines(x=confidence.intervals$density, y=confidence.intervals$lower, col=ci.col, lty=line.type, lwd=line.width)
lines(x=confidence.intervals$density, y=confidence.intervals$upper, col=ci.col, lty=line.type, lwd=line.width)
prediction.intervals <- data.frame(density=df.log.avg$density, lower=LogGainPiLower(df.log.avg$density), upper=LogGainPiUpper(df.log.avg$density))
points(x=prediction.intervals$density, y=prediction.intervals$lower, col=pi.col, pch=symbol, cex=size)
points(x=prediction.intervals$density, y=prediction.intervals$upper, col=pi.col, pch=symbol, cex=size)
lines(x=prediction.intervals$density, y=prediction.intervals$lower, col=pi.col, lty=line.type, lwd=line.width)
lines(x=prediction.intervals$density, y=prediction.intervals$upper, col=pi.col, lty=line.type, lwd=line.width)
legend('topright', legend=c('Least Squares Regression Line', '95% Confidence Interval Bands', '95% Prediction Interval Bands'), col=c('red', ci.col, pi.col), lty=1)

# 95% prediction and confidence intervals of density using gain
end.points <- c(-1, 3) # Interval to search the root in
DensityCi <- function(gain) {
lower <- uniroot(function(density) log(gain) - LogGainCiLower(density), interval=end.points)[[1]]
upper <- uniroot(function(density) log(gain) - LogGainCiUpper(density), interval=end.points)[[1]]
c(lower, upper)
}
DensityPi <- function(gain) {
lower <- uniroot(function(density) log(gain) - LogGainPiLower(density), end.points)[[1]]
upper <- uniroot(function(density) log(gain) - LogGainPiUpper(density), end.points)[[1]]
c(lower, upper)
}
# Point and interval estimates for example gain readings
PredictDensityLeastSquares(38.6) # 38.6 is the average gain for 0.508 density
[1] 0.5089113
PredictDensityLad(38.6)
[1] 0.5076298
PredictDensityQuant(38.6)
[1] 0.5076298
DensityCi(38.6)
[1] 0.5011879 0.5169000
DensityPi(38.6)
[1] 0.4889568 0.5291323
PredictDensityLeastSquares(426.7) # 426.7 is the average gain for 0.001 density
[1] -0.01276954
PredictDensityLad(426.7)
[1] -0.01546807
PredictDensityQuant(426.7)
[1] -0.01546811
DensityCi(426.7)
[1] -0.024285866 -0.001769193
DensityPi(426.7)
[1] -0.034644666 0.008618629
Scenario 3: Cross-Validation
To check how well your procedure works, omit the set of measurements corresponding to the block of density 0.508, apply your “estimation”/calibration procedure to the remaining data, and provide an interval estimate for the density of a block with an average reading of 38.6. Where does the actual density fall in the interval? Try the same test, for the set of measurements at the 0.001 density.
for (omitted in c(0.508, 0.001)) {
# Omit measurements corresponding to the specified density
df.log.omitted = df.log[which(df.log['density'] != omitted), ]
df.log.avg.omitted <- df.log.avg[which(df.log.avg['density'] != omitted), ]
# Redo calculations using modified dataset
least.squares <- lm(gain~density, data=df.log.avg.omitted)
mean.density <- mean(df.log.avg.omitted$density)
summation <- sum((df.log.avg.omitted$density - mean.density) ^ 2)
s2 <- aggregate(list(variance=least.squares.residuals$gain), by=list(density=least.squares.residuals$density), FUN=var)
s.pooled <- sqrt(mean(s2$variance))
ci.width.expr <- quote(width <- t * s.pooled * sqrt(1/(m-1) + (density-mean.density)^2 / summation))
pi.width.expr <- quote(width <- t * s.pooled * sqrt(1 + 1/(m-1) + (density-mean.density)^2 / summation))
plot(df.log.omitted, main=title, xlab=x.axis, ylab=y.log.axis, xlim=x.range, ylim=y.range)
abline(least.squares, col='red')
ci.col <- 'purple'
pi.col <- 'blue'
symbol <- '-'
size <- 1.5
line.type <- 3
line.width <- 0.7
confidence.intervals <- data.frame(density=df.log.avg.omitted$density, lower=LogGainCiLower(df.log.avg.omitted$density), upper=LogGainCiUpper(df.log.avg.omitted$density))
points(x=confidence.intervals$density, y=confidence.intervals$lower, col=ci.col, pch=symbol, cex=size)
points(x=confidence.intervals$density, y=confidence.intervals$upper, col=ci.col, pch=symbol, cex=size)
lines(x=confidence.intervals$density, y=confidence.intervals$lower, col=ci.col, lty=line.type, lwd=line.width)
lines(x=confidence.intervals$density, y=confidence.intervals$upper, col=ci.col, lty=line.type, lwd=line.width)
prediction.intervals <- data.frame(density=df.log.avg.omitted$density, lower=LogGainPiLower(df.log.avg.omitted$density), upper=LogGainPiUpper(df.log.avg.omitted$density))
points(x=prediction.intervals$density, y=prediction.intervals$lower, col=pi.col, pch=symbol, cex=size)
points(x=prediction.intervals$density, y=prediction.intervals$upper, col=pi.col, pch=symbol, cex=size)
lines(x=prediction.intervals$density, y=prediction.intervals$lower, col=pi.col, lty=line.type, lwd=line.width)
lines(x=prediction.intervals$density, y=prediction.intervals$upper, col=pi.col, lty=line.type, lwd=line.width)
legend('topright', legend=c('Least Squares Regression Line', '95% Confidence Interval Bands', '95% Prediction Interval Bands'), col=c('red', ci.col, pi.col), lty=1)
print(PredictDensityLeastSquares(38.6)) # 38.6 is the average gain for 0.508 density
print(DensityCi(38.6))
print(DensityPi(38.6))
print(PredictDensityLeastSquares(426.7)) # 426.7 is the average gain for 0.001 density
print(DensityCi(426.7))
print(DensityPi(426.7))
}
[1] 0.5091927
[1] 0.5006695 0.5180406
[1] 0.4889184 0.5297925
[1] -0.0128045
[1] -0.024342164 -0.001790802
[1] -0.034760736 0.008598777

[1] 0.5092919
[1] 0.5014370 0.5174323
[1] 0.4890257 0.5298473
[1] -0.02051733
[1] -0.035344991 -0.006521825
[1] -0.044604850 0.002738127

Additional Scenario: Temperature, DOY, and Latitude.
Use the additional dataset to construct a model fitting temperature with DOY, latitude, and other reasonable features. Try sketching the least squares line on a scatter plot. We aim to investigate the relationship between temperature and the DOY, and its latitude.
# Check the correlation
data <- read.csv('Full Resolution Data/64506420.csv', header=TRUE)
data <- data[,c('Hour','DOY','POS_DOY','Lat','Lon','Ts','BP')]
# Drop the extreme outlier case
#data <- data[which(data$Ts>-200),]
data_matrix <- as.matrix(data)
# Correlation Matrix
corr_matrix <- cor(data_matrix)
corr_matrix
Hour DOY POS_DOY Lat Lon Ts BP
Hour 1.000000000 -0.006779489 -0.006775002 -0.007511024 0.01217860 0.008592576 0.02505819
DOY -0.006779489 1.000000000 0.999999958 0.903704617 -0.68012490 -0.906918658 -0.17232397
POS_DOY -0.006775002 0.999999958 1.000000000 0.903696909 -0.68013445 -0.906916964 -0.17230481
Lat -0.007511024 0.903704617 0.903696909 1.000000000 -0.59748962 -0.958835395 -0.29666821
Lon 0.012178596 -0.680124899 -0.680134450 -0.597489620 1.00000000 0.564136723 -0.02981279
Ts 0.008592576 -0.906918658 -0.906916964 -0.958835395 0.56413672 1.000000000 0.20223136
BP 0.025058188 -0.172323969 -0.172304805 -0.296668215 -0.02981279 0.202231363 1.00000000
# Group by DOY and average replicated measurements
data$DOY <- round(data$DOY,0)
data.avg = aggregate(list(data=data[,c('Ts','Lat')]), by=list(DOY=data$DOY), FUN=mean)
# least squares line
ggplot(data.avg,aes(x=data.avg$DOY, y=data.avg$data.Ts)) +
geom_point(color='#2980B9', size = 4) +
geom_smooth(method=lm, color='#2C3E50') +ggtitle(label ="Least Squares Regression Line") + xlab("Day Of Year") +
ylab("Temperature")

fit1<-lm(formula = data.Ts ~ DOY, data = data.avg)
summary(fit1)
Call:
lm(formula = data.Ts ~ DOY, data = data.avg)
Residuals:
Min 1Q Median 3Q Max
-0.95439 -0.34633 0.03139 0.35247 0.84693
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 9.353218 0.114242 81.87 <2e-16 ***
DOY -0.040253 0.001989 -20.23 <2e-16 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 0.4741 on 86 degrees of freedom
Multiple R-squared: 0.8264, Adjusted R-squared: 0.8244
F-statistic: 409.4 on 1 and 86 DF, p-value: < 2.2e-16
ggplot(data.avg,aes(x=data.avg$data.Lat, y=data.avg$data.Ts)) +
geom_point(color='#2980B9', size = 4) +
geom_smooth(method=lm, color='#2C3E50') +ggtitle(label ="Least Squares Regression Line")+ xlab("Lattitude") +
ylab("Temperature")

fit2<-lm(formula = data.Ts ~ data.Lat, data = data.avg)
summary(fit2)
Call:
lm(formula = data.Ts ~ data.Lat, data = data.avg)
Residuals:
Min 1Q Median 3Q Max
-0.51377 -0.26924 -0.04201 0.24403 0.64902
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 47.9901 1.2672 37.87 <2e-16 ***
data.Lat -0.6202 0.0193 -32.14 <2e-16 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 0.3155 on 86 degrees of freedom
Multiple R-squared: 0.9231, Adjusted R-squared: 0.9222
F-statistic: 1033 on 1 and 86 DF, p-value: < 2.2e-16
# Polynomial Regression Line
fit3<-lm(formula = data.Ts ~ DOY + data.Lat, data = data.avg)
summary(fit3)
Call:
lm(formula = data.Ts ~ DOY + data.Lat, data = data.avg)
Residuals:
Min 1Q Median 3Q Max
-0.60721 -0.22496 -0.00537 0.25822 0.61356
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 40.049638 2.673979 14.978 < 2e-16 ***
DOY -0.009756 0.002936 -3.322 0.00132 **
data.Lat -0.491566 0.042805 -11.484 < 2e-16 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 0.2985 on 85 degrees of freedom
Multiple R-squared: 0.932, Adjusted R-squared: 0.9304
F-statistic: 582.1 on 2 and 85 DF, p-value: < 2.2e-16
qqnorm(fit2$residuals, main=paste('Normal Q-Q Plot with', title.residuals1), cex.main=1)
qqline(fit2$residuals, col='red')

title.residuals1 <- 'Residuals of Least Square Regression Line'
plot(fit2$residuals, main=title.residuals1, ylab = "Standardized Residuals")
abline(0, 0, col='red')

LS0tCnRpdGxlOiAnQ0FTRSBTVFVEWSA0OicKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKVGhpcyBzdHVkeSBhbGxvd3MgdXMgdG8gcmV2aXNpdC9yZW5ldwoKMS4gUmVncmVzc2lvbiBtb2RlbGluZwoyLiBQcm9wZXJ0aWVzIG9mIExlYXN0IFNxdWFyZXMvRml0dGluZyAiYSBsaW5lIgozLiBNdWx0aXBsZSBvYnNlcnZhdGlvbgoKRGF0YXNldHMgZm9yIHRoaXMgc3R1ZHkgYXJlCgoxLiBUaGUgbWFpbiBmaWxlOiBnYXVnZS50eHQKMi4gU3VwcGxlbWVudGFyeSBsYXJnZS1zY2FsZSBmaWxlczogZG93bmxvYWQgdGhlIGZvbGxvd2luZyBmb2xkZXIgRnVsbCBSZXNvbHV0aW9uIERhdGEuemlwIE1vcmUgaW5mb3JtYXRpb24gYWJvdXQgdGhlIHN1cHBsZW1lbnRhcnkgZmlsZSBjYW4gYmUgZm91bmQgYXQgaHR0cDovL2lhYnAuYXBsLndhc2hpbmd0b24uZWR1L2RhdGEuaHRtbCBhcyB3ZWxsIGFzIGh0dHA6Ly9uc2lkYy5vcmcvZGF0YS9HMDA3OTEKCgojIyBRdWVzdGlvbgpUaGUgYWltIG9mIHRoaXMgbGFiIGlzIHRvIHByb3ZpZGUgYSBzaW1wbGUgcHJvY2VkdXJlIGZvciBjb252ZXJ0aW5nIGdhaW4gaW50byBkZW5zaXR5IHdoZW4gdGhlIGdhdWdlIGlzIGluIG9wZXJhdGlvbi4gS2VlcCBpbiBtaW5kIHRoYXQgdGhlIGV4cGVyaW1lbnQgd2FzIGNvbmR1Y3RlZCBieSB2YXJ5aW5nIGRlbnNpdHkgYW5kIG1lYXN1cmluZyB0aGUgcmVzcG9uc2UgaW4gZ2FpbiwgYnV0IHdoZW4gdGhlIGdhdWdlIGlzIHVsdGltYXRlbHkgaW4gdXNlLCB0aGUgc25vdy1wYWNrIGRlbnNpdHkgaXMgdG8gYmUgZXN0aW1hdGVkIGZyb20gdGhlIG1lYXN1cmVkIGdhaW4uCgoKIyMgU2V0dXAKYGBge3J9CmRmIDwtIHJlYWQudGFibGUoJ2dhdWdlLTF3YjF3YTYtMmdwZWw0MS50eHQnLCBoZWFkZXI9VFJVRSkKZGYgPC0gZGZbb3JkZXIoZGYkZGVuc2l0eSksIF0gICMgU29ydCBmcm9tIGxlYXN0IHRvIGdyZWF0ZXN0IGRlbnNpdHkKCm0gPC0gOSAgIyBOdW1iZXIgb2YgZGlzdGluY3QgYmxvY2sgZGVuc2l0aWVzCnQgPC0gMTAgICMgTnVtYmVyIG9mIHJlcGxpY2F0ZSBtZWFzdXJlbWVudHMKCiNpbnN0YWxsLnBhY2thZ2VzKCdMMXBhY2snKQojaW5zdGFsbC5wYWNrYWdlcygncXVhbnRyZWcnKQojaW5zdGFsbC5wYWNrYWdlcygnZ2dwbG90MicpCmxpYnJhcnkoTDFwYWNrKSAgIyBVc2VkIGZvciBsZWFzdCBhYnNvbHV0ZSBkZXZpYXRpb25zIHJlZ3Jlc3Npb24gbGluZQpsaWJyYXJ5KHF1YW50cmVnKSAgIyBVc2VkIGZvciBxdWFudGlsZSByZWdyZXNzaW9uIGxpbmUKbGlicmFyeShnZ3Bsb3QyKQpgYGAKCgojIyBTY2VuYXJpbyAxOiBGaXR0aW5nClVzZSB0aGUgZGF0YSB0byBmaXQgdGhlIGdhaW4sIG9yIGEgdHJhbnNmb3JtYXRpb24gb2YgZ2FpbiwgdG8gZGVuc2l0eS4gVHJ5IHNrZXRjaGluZyB0aGUgbGVhc3Qgc3F1YXJlcyBsaW5lIG9uIGEgc2NhdHRlciBwbG90LgoKKiBEbyB0aGUgcmVzaWR1YWxzIGluZGljYXRlIGFueSBwcm9ibGVtcyB3aXRoIHRoZSBmaXQ/CiogSWYgdGhlIGRlbnNpdGllcyBvZiB0aGUgcG9seWV0aHlsZW5lIGJsb2NrcyBhcmUgbm90IHJlcG9ydGVkIGV4YWN0bHksIGhvdyBtaWdodCB0aGlzIGFmZmVjdCB0aGUgZml0PwoqIFdoYXQgaWYgdGhlIGJsb2NrcyBvZiBwb2x5ZXRoeWxlbmUgd2VyZSBub3QgbWVhc3VyZWQgaW4gcmFuZG9tIG9yZGVyIChsb2NhdGlvbik/CmBgYHtyfQojIFBsb3QgcmF3IGRhdGEKdGl0bGUgPC0gJ0RlbnNpdHkgdnMuIEdhaW4nCnguYXhpcyA8LSBleHByZXNzaW9uKCdEZW5zaXR5IChnL2NtJ14zKicpJykKeS5heGlzIDwtICdHYWluJwp4LnJhbmdlIDwtIGMoMCwgLjcpCnkucmFuZ2UgPC0gYygyLjUsIDYpCnBsb3QoZGYsIG1haW49dGl0bGUsIHhsYWI9eC5heGlzLCB5bGFiPXkuYXhpcywgeGxpbT14LnJhbmdlKQoKCiMgVGFrZSBsb2cgdHJhbnNmb3JtYXRpb24gb2YgcmVzcG9uc2UgdmFyaWFibGUgKGdhaW4pCnkubG9nLmF4aXMgPSAnbG9nKEdhaW4pJwpkZi5sb2cgPSBkYXRhLmZyYW1lKGRmWydkZW5zaXR5J10sIGxvZyhkZlsnZ2FpbiddKSkKcGxvdChkZi5sb2csIG1haW49dGl0bGUsIHhsYWI9eC5heGlzLCB5bGFiPXkubG9nLmF4aXMsIHhsaW09eC5yYW5nZSwgeWxpbT15LnJhbmdlKQoKCiMgQXZlcmFnZSByZXBsaWNhdGUgbWVhc3VyZW1lbnRzCmRmLmxvZy5hdmcgPSBhZ2dyZWdhdGUobGlzdChnYWluPWRmLmxvZyRnYWluKSwgYnk9bGlzdChkZW5zaXR5PWRmLmxvZyRkZW5zaXR5KSwgRlVOPW1lYW4pCnBsb3QoZGYubG9nLmF2ZywgbWFpbj10aXRsZSwgeGxhYj14LmF4aXMsIHlsYWI9eS5sb2cuYXhpcywgeGxpbT14LnJhbmdlLCB5bGltPXkucmFuZ2UpCgoKIyBGaXQgZ2FpbiB0byBkZW5zaXR5CmxlYXN0LnNxdWFyZXMgPC0gbG0oZ2Fpbn5kZW5zaXR5LCBkYXRhPWRmLmxvZy5hdmcpCmxhZCA8LSBsYWQoZ2Fpbn5kZW5zaXR5LCBkYXRhPWRmLmxvZy5hdmcpCnF1YW50IDwtIHJxKGdhaW5+ZGVuc2l0eSwgdGF1PS41LCBkYXRhPWRmLmxvZy5hdmcpCgpwbG90KGRmLmxvZywgbWFpbj10aXRsZSwgeGxhYj14LmF4aXMsIHlsYWI9eS5sb2cuYXhpcywgeGxpbT14LnJhbmdlLCB5bGltPXkucmFuZ2UpCmFibGluZShsZWFzdC5zcXVhcmVzLCBjb2w9J3JlZCcpCmFibGluZShsYWQsIGNvbD0nYmx1ZScpCmFibGluZShxdWFudCwgY29sPSdncmVlbicpCmxlZ2VuZCgndG9wcmlnaHQnLCBsZWdlbmQ9YygnTGVhc3QgU3F1YXJlcyBSZWdyZXNzaW9uIExpbmUnLCAnTGVhc3QgQWJzb2x1dGUgRGV2aWF0aW9ucyBSZWdyZXNzaW9uIExpbmUnLCAnNTAlIFF1YW50aWxlIFJlZ3Jlc3Npb24gTGluZScpLCBjb2w9YygncmVkJywgJ2JsdWUnLCAnZ3JlZW4nKSwgbHR5PTEpCgpjKGNvcihkZi5sb2cuYXZnKSwgc3VtbWFyeShsZWFzdC5zcXVhcmVzKSRyLnNxdWFyZWQpCmxlYXN0LnNxdWFyZXMKbGFkCnF1YW50CgoKIyBDaGVjayBjb25kaXRpb25zIGZvciBsaW5lYXIgcmVncmVzc2lvbjogbGluZWFyaXR5LCBub3JtYWxpdHkgb2YgcmVzaWR1YWxzLCBhbmQgY29uc3RhbnQgdmFyaWFiaWxpdHkKbGVhc3Quc3F1YXJlcy5yZXNpZHVhbHMgPC0gZGF0YS5mcmFtZShkZi5sb2dbJ2RlbnNpdHknXSwgZGYubG9nWydnYWluJ10gLSByZXAocHJlZGljdChsZWFzdC5zcXVhcmVzKSwgZWFjaD0xMCkpCmxhZC5yZXNpZHVhbHMgPC0gZGF0YS5mcmFtZShkZi5sb2dbJ2RlbnNpdHknXSwgZGYubG9nWydnYWluJ10gLSByZXAocHJlZGljdChsYWQpLCBlYWNoPTEwKSkKcXVhbnQucmVzaWR1YWxzIDwtIGRhdGEuZnJhbWUoZGYubG9nWydkZW5zaXR5J10sIGRmLmxvZ1snZ2FpbiddIC0gcmVwKHByZWRpY3QocXVhbnQpLCBlYWNoPTEwKSkKCnRpdGxlLnJlc2lkdWFsczEgPC0gJ1Jlc2lkdWFscyBvZiBMZWFzdCBTcXVhcmVzIFJlZ3Jlc3Npb24gTGluZScKdGl0bGUucmVzaWR1YWxzMiA8LSAnUmVzaWR1YWxzIG9mIExlYXN0IEFic29sdXRlIERldmlhdGlvbnMgUmVncmVzc2lvbiBMaW5lJwp0aXRsZS5yZXNpZHVhbHMzIDwtICdSZXNpZHVhbHMgb2YgNTAlIFF1YW50aWxlIFJlZ3Jlc3Npb24gTGluZScKcGxvdChsZWFzdC5zcXVhcmVzLnJlc2lkdWFscyRnYWluLCBtYWluPXRpdGxlLnJlc2lkdWFsczEsIHlsYWI9eS5heGlzKQphYmxpbmUoMCwgMCwgY29sPSdyZWQnKQpwbG90KGxhZC5yZXNpZHVhbHMkZ2FpbiwgbWFpbj10aXRsZS5yZXNpZHVhbHMyLCB5bGFiPXkuYXhpcykKYWJsaW5lKDAsIDAsIGNvbD0nYmx1ZScpCnBsb3QocXVhbnQucmVzaWR1YWxzJGdhaW4sIG1haW49dGl0bGUucmVzaWR1YWxzMywgeWxhYj15LmF4aXMpCmFibGluZSgwLCAwLCBjb2w9J2dyZWVuJykKCm51bS5iaW5zIDwtIDEyCmhpc3QobGVhc3Quc3F1YXJlcy5yZXNpZHVhbHMkZ2FpbiwgYnJlYWtzPW51bS5iaW5zLCBtYWluPXRpdGxlLnJlc2lkdWFsczEsIHhsYWI9eS5heGlzLCBjb2w9J3JlZCcpCmhpc3QobGFkLnJlc2lkdWFscyRnYWluLCBicmVha3M9bnVtLmJpbnMsIG1haW49dGl0bGUucmVzaWR1YWxzMiwgeGxhYj15LmF4aXMsIGNvbD0nYmx1ZScpCmhpc3QocXVhbnQucmVzaWR1YWxzJGdhaW4sIGJyZWFrcz1udW0uYmlucywgbWFpbj10aXRsZS5yZXNpZHVhbHMzLCB4bGFiPXkuYXhpcywgY29sPSdncmVlbicpCgpxcW5vcm0obGVhc3Quc3F1YXJlcy5yZXNpZHVhbHMkZ2FpbiwgbWFpbj1wYXN0ZSgnTm9ybWFsIFEtUSBQbG90IHdpdGgnLCB0aXRsZS5yZXNpZHVhbHMxKSwgY2V4Lm1haW49MSkKcXFsaW5lKGxlYXN0LnNxdWFyZXMucmVzaWR1YWxzJGdhaW4sIGNvbD0ncmVkJykKcXFub3JtKGxhZC5yZXNpZHVhbHMkZ2FpbiwgbWFpbj1wYXN0ZSgnTm9ybWFsIFEtUSBQbG90IHdpdGgnLCB0aXRsZS5yZXNpZHVhbHMyKSwgY2V4Lm1haW49MSkKcXFsaW5lKGxhZC5yZXNpZHVhbHMkZ2FpbiwgY29sPSdibHVlJykKcXFub3JtKHF1YW50LnJlc2lkdWFscyRnYWluLCBtYWluPXBhc3RlKCdOb3JtYWwgUS1RIFBsb3Qgd2l0aCcsIHRpdGxlLnJlc2lkdWFsczMpLCBjZXgubWFpbj0xKQpxcWxpbmUocXVhbnQucmVzaWR1YWxzJGdhaW4sIGNvbD0nZ3JlZW4nKQpgYGAKCgojIyBTY2VuYXJpbyAyOiBQcmVkaWN0aW5nClVsdGltYXRlbHkgd2UgYXJlIGludGVyZXN0ZWQgaW4gYW5zd2VyaW5nIHF1ZXN0aW9ucyBzdWNoIGFzOiBHaXZlbiBhIGdhaW4gcmVhZGluZyBvZiAzOC42LCB3aGF0IGlzIHRoZSBkZW5zaXR5IG9mIHRoZSBzbm93LXBhY2s/IEdpdmVuIGEgZ2FpbiByZWFkaW5nIG9mIDQyNi43LCB3aGF0IGlzIHRoZSBkZW5zaXR5IG9mIHRoZSBzbm93LXBhY2s/IFRoZXNlIHR3byBudW1lcmljIHZhbHVlcywgMzguNiBhbmQgNDI2LjcsIHdlcmUgY2hvc2VuIGJlY2F1c2UgdGhleSBhcmUgdGhlIGF2ZXJhZ2UgZ2FpbnMgZm9yIHRoZSAwLjUwOCBhbmQgMC4wMDEgZGVuc2l0aWVzLCByZXNwZWN0aXZlbHkuCgoqIERldmVsb3AgYSBwcm9jZWR1cmUgZm9yIGFkZGluZyBiYW5kcyBhcm91bmQgeW91ciBsZWFzdCBzcXVhcmVzIGxpbmUgdGhhdCBjYW4gYmUgdXNlZCB0byBtYWtlIGludGVydmFsIGVzdGltYXRlcyBmb3IgdGhlIHNub3ctcGFjayBkZW5zaXR5IGZyb20gZ2FpbiBtZWFzdXJlbWVudHMuIEtlZXAgaW4gbWluZCBob3cgdGhlIGRhdGEgd2VyZSBjb2xsZWN0ZWQ6IHNldmVyYWwgbWVhc3VyZW1lbnRzIG9mIGdhaW4gd2VyZSB0YWtlbiBmb3IgcG9seWVueXRoeWxlbmUgYmxvY2tzIG9mIGtub3duIGRlbnNpdHkuCmBgYHtyIGZpZy5hc3A9MiwgZmlnLndpZHRoPTV9CiMgUHJlZGljdGlvbnMKUHJlZGljdExvZ0dhaW4gPC0gZnVuY3Rpb24oZGVuc2l0eSkKICBwcmVkaWN0KGxlYXN0LnNxdWFyZXMsIGRhdGEuZnJhbWUoZGVuc2l0eT1kZW5zaXR5KSkgICMgUHJlZGljdCBsb2coZ2FpbikgdXNpbmcgZGVuc2l0eQoKUHJlZGljdERlbnNpdHlMZWFzdFNxdWFyZXMgPC0gZnVuY3Rpb24oZ2FpbikgewogIGludGVyY2VwdCA8LSBjb2VmKGxlYXN0LnNxdWFyZXMpW1sxXV0KICBzbG9wZSA8LSBjb2VmKGxlYXN0LnNxdWFyZXMpW1syXV0KICAobG9nKGdhaW4pIC0gaW50ZXJjZXB0KSAvIHNsb3BlICAjIFByZWRpY3QgZGVuc2l0eSB1c2luZyBnYWluCn0KClByZWRpY3REZW5zaXR5TGFkIDwtIGZ1bmN0aW9uKGdhaW4pIHsKICBpbnRlcmNlcHQgPC0gY29lZihsYWQpW1sxXV0KICBzbG9wZSA8LSBjb2VmKGxhZClbWzJdXQogIChsb2coZ2FpbikgLSBpbnRlcmNlcHQpIC8gc2xvcGUgICMgUHJlZGljdCBkZW5zaXR5IHVzaW5nIGdhaW4KfQoKUHJlZGljdERlbnNpdHlRdWFudCA8LSBmdW5jdGlvbihnYWluKSB7CiAgaW50ZXJjZXB0IDwtIGNvZWYocXVhbnQpW1sxXV0KICBzbG9wZSA8LSBjb2VmKHF1YW50KVtbMl1dCiAgKGxvZyhnYWluKSAtIGludGVyY2VwdCkgLyBzbG9wZSAgIyBQcmVkaWN0IGRlbnNpdHkgdXNpbmcgZ2Fpbgp9CgoKIyA5NSUgcHJlZGljdGlvbiBhbmQgY29uZmlkZW5jZSBpbnRlcnZhbHMgb2YgbG9nKGdhaW4pIHVzaW5nIGRlbnNpdHkKdCA8LSBxdCguOTc1LCBkZj1tLTIpCm1lYW4uZGVuc2l0eSA8LSBtZWFuKGRmLmxvZy5hdmckZGVuc2l0eSkKc3VtbWF0aW9uIDwtIHN1bSgoZGYubG9nLmF2ZyRkZW5zaXR5IC0gbWVhbi5kZW5zaXR5KSBeIDIpCgpzMiA8LSBhZ2dyZWdhdGUobGlzdCh2YXJpYW5jZT1sZWFzdC5zcXVhcmVzLnJlc2lkdWFscyRnYWluKSwgYnk9bGlzdChkZW5zaXR5PWxlYXN0LnNxdWFyZXMucmVzaWR1YWxzJGRlbnNpdHkpLCBGVU49dmFyKQpzLnBvb2xlZCA8LSBzcXJ0KG1lYW4oczIkdmFyaWFuY2UpKQoKY2VudGVyLmV4cHIgPC0gcXVvdGUoY2VudGVyIDwtIFByZWRpY3RMb2dHYWluKGRlbnNpdHkpKQpjaS53aWR0aC5leHByIDwtIHF1b3RlKHdpZHRoIDwtIHQgKiBzLnBvb2xlZCAqIHNxcnQoMS9tICsgKGRlbnNpdHktbWVhbi5kZW5zaXR5KV4yIC8gc3VtbWF0aW9uKSkKcGkud2lkdGguZXhwciA8LSBxdW90ZSh3aWR0aCA8LSB0ICogcy5wb29sZWQgKiBzcXJ0KDEgKyAxL20gKyAoZGVuc2l0eS1tZWFuLmRlbnNpdHkpXjIgLyBzdW1tYXRpb24pKQoKTG9nR2FpbkNpTG93ZXIgPC0gZnVuY3Rpb24oZGVuc2l0eSkgewogIGV2YWwoY2VudGVyLmV4cHIpCiAgZXZhbChjaS53aWR0aC5leHByKQogIGNlbnRlciAtIHdpZHRoCn0KCkxvZ0dhaW5DaVVwcGVyIDwtIGZ1bmN0aW9uKGRlbnNpdHkpIHsKICBldmFsKGNlbnRlci5leHByKQogIGV2YWwoY2kud2lkdGguZXhwcikKICBjZW50ZXIgKyB3aWR0aAp9CgpMb2dHYWluUGlMb3dlciA8LSBmdW5jdGlvbihkZW5zaXR5KSB7CiAgZXZhbChjZW50ZXIuZXhwcikKICBldmFsKHBpLndpZHRoLmV4cHIpCiAgY2VudGVyIC0gd2lkdGgKfQoKTG9nR2FpblBpVXBwZXIgPC0gZnVuY3Rpb24oZGVuc2l0eSkgewogIGV2YWwoY2VudGVyLmV4cHIpCiAgZXZhbChwaS53aWR0aC5leHByKQogIGNlbnRlciArIHdpZHRoCn0KCiMgQWRkIGJhbmRzIGFyb3VuZCBsZWFzdCBzcXVhcmVzIGxpbmUKcGxvdChkZi5sb2csIG1haW49dGl0bGUsIHhsYWI9eC5heGlzLCB5bGFiPXkubG9nLmF4aXMsIHhsaW09eC5yYW5nZSwgeWxpbT15LnJhbmdlKQphYmxpbmUobGVhc3Quc3F1YXJlcywgY29sPSdyZWQnKQoKY2kuY29sIDwtICdwdXJwbGUnCnBpLmNvbCA8LSAnYmx1ZScKc3ltYm9sIDwtICctJwpzaXplIDwtIDEuNQpsaW5lLnR5cGUgPC0gMwpsaW5lLndpZHRoIDwtIDAuNwoKY29uZmlkZW5jZS5pbnRlcnZhbHMgPC0gZGF0YS5mcmFtZShkZW5zaXR5PWRmLmxvZy5hdmckZGVuc2l0eSwgbG93ZXI9TG9nR2FpbkNpTG93ZXIoZGYubG9nLmF2ZyRkZW5zaXR5KSwgdXBwZXI9TG9nR2FpbkNpVXBwZXIoZGYubG9nLmF2ZyRkZW5zaXR5KSkKcG9pbnRzKHg9Y29uZmlkZW5jZS5pbnRlcnZhbHMkZGVuc2l0eSwgeT1jb25maWRlbmNlLmludGVydmFscyRsb3dlciwgY29sPWNpLmNvbCwgcGNoPXN5bWJvbCwgY2V4PXNpemUpCnBvaW50cyh4PWNvbmZpZGVuY2UuaW50ZXJ2YWxzJGRlbnNpdHksIHk9Y29uZmlkZW5jZS5pbnRlcnZhbHMkdXBwZXIsIGNvbD1jaS5jb2wsIHBjaD1zeW1ib2wsIGNleD1zaXplKQpsaW5lcyh4PWNvbmZpZGVuY2UuaW50ZXJ2YWxzJGRlbnNpdHksIHk9Y29uZmlkZW5jZS5pbnRlcnZhbHMkbG93ZXIsIGNvbD1jaS5jb2wsIGx0eT1saW5lLnR5cGUsIGx3ZD1saW5lLndpZHRoKQpsaW5lcyh4PWNvbmZpZGVuY2UuaW50ZXJ2YWxzJGRlbnNpdHksIHk9Y29uZmlkZW5jZS5pbnRlcnZhbHMkdXBwZXIsIGNvbD1jaS5jb2wsIGx0eT1saW5lLnR5cGUsIGx3ZD1saW5lLndpZHRoKQoKcHJlZGljdGlvbi5pbnRlcnZhbHMgPC0gZGF0YS5mcmFtZShkZW5zaXR5PWRmLmxvZy5hdmckZGVuc2l0eSwgbG93ZXI9TG9nR2FpblBpTG93ZXIoZGYubG9nLmF2ZyRkZW5zaXR5KSwgdXBwZXI9TG9nR2FpblBpVXBwZXIoZGYubG9nLmF2ZyRkZW5zaXR5KSkKcG9pbnRzKHg9cHJlZGljdGlvbi5pbnRlcnZhbHMkZGVuc2l0eSwgeT1wcmVkaWN0aW9uLmludGVydmFscyRsb3dlciwgY29sPXBpLmNvbCwgcGNoPXN5bWJvbCwgY2V4PXNpemUpCnBvaW50cyh4PXByZWRpY3Rpb24uaW50ZXJ2YWxzJGRlbnNpdHksIHk9cHJlZGljdGlvbi5pbnRlcnZhbHMkdXBwZXIsIGNvbD1waS5jb2wsIHBjaD1zeW1ib2wsIGNleD1zaXplKQpsaW5lcyh4PXByZWRpY3Rpb24uaW50ZXJ2YWxzJGRlbnNpdHksIHk9cHJlZGljdGlvbi5pbnRlcnZhbHMkbG93ZXIsIGNvbD1waS5jb2wsIGx0eT1saW5lLnR5cGUsIGx3ZD1saW5lLndpZHRoKQpsaW5lcyh4PXByZWRpY3Rpb24uaW50ZXJ2YWxzJGRlbnNpdHksIHk9cHJlZGljdGlvbi5pbnRlcnZhbHMkdXBwZXIsIGNvbD1waS5jb2wsIGx0eT1saW5lLnR5cGUsIGx3ZD1saW5lLndpZHRoKQoKbGVnZW5kKCd0b3ByaWdodCcsIGxlZ2VuZD1jKCdMZWFzdCBTcXVhcmVzIFJlZ3Jlc3Npb24gTGluZScsICc5NSUgQ29uZmlkZW5jZSBJbnRlcnZhbCBCYW5kcycsICc5NSUgUHJlZGljdGlvbiBJbnRlcnZhbCBCYW5kcycpLCBjb2w9YygncmVkJywgY2kuY29sLCBwaS5jb2wpLCBsdHk9MSkKCgojIDk1JSBwcmVkaWN0aW9uIGFuZCBjb25maWRlbmNlIGludGVydmFscyBvZiBkZW5zaXR5IHVzaW5nIGdhaW4KZW5kLnBvaW50cyA8LSBjKC0xLCAzKSAgIyBJbnRlcnZhbCB0byBzZWFyY2ggdGhlIHJvb3QgaW4KCkRlbnNpdHlDaSA8LSBmdW5jdGlvbihnYWluKSB7CiAgbG93ZXIgPC0gdW5pcm9vdChmdW5jdGlvbihkZW5zaXR5KSBsb2coZ2FpbikgLSBMb2dHYWluQ2lMb3dlcihkZW5zaXR5KSwgaW50ZXJ2YWw9ZW5kLnBvaW50cylbWzFdXQogIHVwcGVyIDwtIHVuaXJvb3QoZnVuY3Rpb24oZGVuc2l0eSkgbG9nKGdhaW4pIC0gTG9nR2FpbkNpVXBwZXIoZGVuc2l0eSksIGludGVydmFsPWVuZC5wb2ludHMpW1sxXV0KICBjKGxvd2VyLCB1cHBlcikKfQoKRGVuc2l0eVBpIDwtIGZ1bmN0aW9uKGdhaW4pIHsKICBsb3dlciA8LSB1bmlyb290KGZ1bmN0aW9uKGRlbnNpdHkpIGxvZyhnYWluKSAtIExvZ0dhaW5QaUxvd2VyKGRlbnNpdHkpLCBlbmQucG9pbnRzKVtbMV1dCiAgdXBwZXIgPC0gdW5pcm9vdChmdW5jdGlvbihkZW5zaXR5KSBsb2coZ2FpbikgLSBMb2dHYWluUGlVcHBlcihkZW5zaXR5KSwgZW5kLnBvaW50cylbWzFdXQogIGMobG93ZXIsIHVwcGVyKQp9CgoKIyBQb2ludCBhbmQgaW50ZXJ2YWwgZXN0aW1hdGVzIGZvciBleGFtcGxlIGdhaW4gcmVhZGluZ3MKUHJlZGljdERlbnNpdHlMZWFzdFNxdWFyZXMoMzguNikgICMgMzguNiBpcyB0aGUgYXZlcmFnZSBnYWluIGZvciAwLjUwOCBkZW5zaXR5ClByZWRpY3REZW5zaXR5TGFkKDM4LjYpClByZWRpY3REZW5zaXR5UXVhbnQoMzguNikKRGVuc2l0eUNpKDM4LjYpCkRlbnNpdHlQaSgzOC42KQoKUHJlZGljdERlbnNpdHlMZWFzdFNxdWFyZXMoNDI2LjcpICAjIDQyNi43IGlzIHRoZSBhdmVyYWdlIGdhaW4gZm9yIDAuMDAxIGRlbnNpdHkKUHJlZGljdERlbnNpdHlMYWQoNDI2LjcpClByZWRpY3REZW5zaXR5UXVhbnQoNDI2LjcpCkRlbnNpdHlDaSg0MjYuNykKRGVuc2l0eVBpKDQyNi43KQpgYGAKCgojIyBTY2VuYXJpbyAzOiBDcm9zcy1WYWxpZGF0aW9uClRvIGNoZWNrIGhvdyB3ZWxsIHlvdXIgcHJvY2VkdXJlIHdvcmtzLCBvbWl0IHRoZSBzZXQgb2YgbWVhc3VyZW1lbnRzIGNvcnJlc3BvbmRpbmcgdG8gdGhlIGJsb2NrIG9mIGRlbnNpdHkgMC41MDgsIGFwcGx5IHlvdXIgImVzdGltYXRpb24iL2NhbGlicmF0aW9uIHByb2NlZHVyZSB0byB0aGUgcmVtYWluaW5nIGRhdGEsIGFuZCBwcm92aWRlIGFuIGludGVydmFsIGVzdGltYXRlIGZvciB0aGUgZGVuc2l0eSBvZiBhIGJsb2NrIHdpdGggYW4gYXZlcmFnZSByZWFkaW5nIG9mIDM4LjYuIFdoZXJlIGRvZXMgdGhlIGFjdHVhbCBkZW5zaXR5IGZhbGwgaW4gdGhlIGludGVydmFsPyBUcnkgdGhlIHNhbWUgdGVzdCwgZm9yIHRoZSBzZXQgb2YgbWVhc3VyZW1lbnRzIGF0IHRoZSAwLjAwMSBkZW5zaXR5LgpgYGB7ciBmaWcuYXNwPTIsIGZpZy53aWR0aD01fQpmb3IgKG9taXR0ZWQgaW4gYygwLjUwOCwgMC4wMDEpKSB7CiAgIyBPbWl0IG1lYXN1cmVtZW50cyBjb3JyZXNwb25kaW5nIHRvIHRoZSBzcGVjaWZpZWQgZGVuc2l0eQogIGRmLmxvZy5vbWl0dGVkID0gZGYubG9nW3doaWNoKGRmLmxvZ1snZGVuc2l0eSddICE9IG9taXR0ZWQpLCBdCiAgZGYubG9nLmF2Zy5vbWl0dGVkIDwtIGRmLmxvZy5hdmdbd2hpY2goZGYubG9nLmF2Z1snZGVuc2l0eSddICE9IG9taXR0ZWQpLCBdCiAgCiAgCiAgIyBSZWRvIGNhbGN1bGF0aW9ucyB1c2luZyBtb2RpZmllZCBkYXRhc2V0CiAgbGVhc3Quc3F1YXJlcyA8LSBsbShnYWlufmRlbnNpdHksIGRhdGE9ZGYubG9nLmF2Zy5vbWl0dGVkKQogIAogIG1lYW4uZGVuc2l0eSA8LSBtZWFuKGRmLmxvZy5hdmcub21pdHRlZCRkZW5zaXR5KQogIHN1bW1hdGlvbiA8LSBzdW0oKGRmLmxvZy5hdmcub21pdHRlZCRkZW5zaXR5IC0gbWVhbi5kZW5zaXR5KSBeIDIpCiAgCiAgczIgPC0gYWdncmVnYXRlKGxpc3QodmFyaWFuY2U9bGVhc3Quc3F1YXJlcy5yZXNpZHVhbHMkZ2FpbiksIGJ5PWxpc3QoZGVuc2l0eT1sZWFzdC5zcXVhcmVzLnJlc2lkdWFscyRkZW5zaXR5KSwgRlVOPXZhcikKICBzLnBvb2xlZCA8LSBzcXJ0KG1lYW4oczIkdmFyaWFuY2UpKQogIAogIGNpLndpZHRoLmV4cHIgPC0gcXVvdGUod2lkdGggPC0gdCAqIHMucG9vbGVkICogc3FydCgxLyhtLTEpICsgKGRlbnNpdHktbWVhbi5kZW5zaXR5KV4yIC8gc3VtbWF0aW9uKSkKICBwaS53aWR0aC5leHByIDwtIHF1b3RlKHdpZHRoIDwtIHQgKiBzLnBvb2xlZCAqIHNxcnQoMSArIDEvKG0tMSkgKyAoZGVuc2l0eS1tZWFuLmRlbnNpdHkpXjIgLyBzdW1tYXRpb24pKQogIAogIHBsb3QoZGYubG9nLm9taXR0ZWQsIG1haW49dGl0bGUsIHhsYWI9eC5heGlzLCB5bGFiPXkubG9nLmF4aXMsIHhsaW09eC5yYW5nZSwgeWxpbT15LnJhbmdlKQogIGFibGluZShsZWFzdC5zcXVhcmVzLCBjb2w9J3JlZCcpCiAgCiAgY2kuY29sIDwtICdwdXJwbGUnCiAgcGkuY29sIDwtICdibHVlJwogIHN5bWJvbCA8LSAnLScKICBzaXplIDwtIDEuNQogIGxpbmUudHlwZSA8LSAzCiAgbGluZS53aWR0aCA8LSAwLjcKICAKICBjb25maWRlbmNlLmludGVydmFscyA8LSBkYXRhLmZyYW1lKGRlbnNpdHk9ZGYubG9nLmF2Zy5vbWl0dGVkJGRlbnNpdHksIGxvd2VyPUxvZ0dhaW5DaUxvd2VyKGRmLmxvZy5hdmcub21pdHRlZCRkZW5zaXR5KSwgdXBwZXI9TG9nR2FpbkNpVXBwZXIoZGYubG9nLmF2Zy5vbWl0dGVkJGRlbnNpdHkpKQogIHBvaW50cyh4PWNvbmZpZGVuY2UuaW50ZXJ2YWxzJGRlbnNpdHksIHk9Y29uZmlkZW5jZS5pbnRlcnZhbHMkbG93ZXIsIGNvbD1jaS5jb2wsIHBjaD1zeW1ib2wsIGNleD1zaXplKQogIHBvaW50cyh4PWNvbmZpZGVuY2UuaW50ZXJ2YWxzJGRlbnNpdHksIHk9Y29uZmlkZW5jZS5pbnRlcnZhbHMkdXBwZXIsIGNvbD1jaS5jb2wsIHBjaD1zeW1ib2wsIGNleD1zaXplKQogIGxpbmVzKHg9Y29uZmlkZW5jZS5pbnRlcnZhbHMkZGVuc2l0eSwgeT1jb25maWRlbmNlLmludGVydmFscyRsb3dlciwgY29sPWNpLmNvbCwgbHR5PWxpbmUudHlwZSwgbHdkPWxpbmUud2lkdGgpCiAgbGluZXMoeD1jb25maWRlbmNlLmludGVydmFscyRkZW5zaXR5LCB5PWNvbmZpZGVuY2UuaW50ZXJ2YWxzJHVwcGVyLCBjb2w9Y2kuY29sLCBsdHk9bGluZS50eXBlLCBsd2Q9bGluZS53aWR0aCkKICAKICBwcmVkaWN0aW9uLmludGVydmFscyA8LSBkYXRhLmZyYW1lKGRlbnNpdHk9ZGYubG9nLmF2Zy5vbWl0dGVkJGRlbnNpdHksIGxvd2VyPUxvZ0dhaW5QaUxvd2VyKGRmLmxvZy5hdmcub21pdHRlZCRkZW5zaXR5KSwgdXBwZXI9TG9nR2FpblBpVXBwZXIoZGYubG9nLmF2Zy5vbWl0dGVkJGRlbnNpdHkpKQogIHBvaW50cyh4PXByZWRpY3Rpb24uaW50ZXJ2YWxzJGRlbnNpdHksIHk9cHJlZGljdGlvbi5pbnRlcnZhbHMkbG93ZXIsIGNvbD1waS5jb2wsIHBjaD1zeW1ib2wsIGNleD1zaXplKQogIHBvaW50cyh4PXByZWRpY3Rpb24uaW50ZXJ2YWxzJGRlbnNpdHksIHk9cHJlZGljdGlvbi5pbnRlcnZhbHMkdXBwZXIsIGNvbD1waS5jb2wsIHBjaD1zeW1ib2wsIGNleD1zaXplKQogIGxpbmVzKHg9cHJlZGljdGlvbi5pbnRlcnZhbHMkZGVuc2l0eSwgeT1wcmVkaWN0aW9uLmludGVydmFscyRsb3dlciwgY29sPXBpLmNvbCwgbHR5PWxpbmUudHlwZSwgbHdkPWxpbmUud2lkdGgpCiAgbGluZXMoeD1wcmVkaWN0aW9uLmludGVydmFscyRkZW5zaXR5LCB5PXByZWRpY3Rpb24uaW50ZXJ2YWxzJHVwcGVyLCBjb2w9cGkuY29sLCBsdHk9bGluZS50eXBlLCBsd2Q9bGluZS53aWR0aCkKICAKICBsZWdlbmQoJ3RvcHJpZ2h0JywgbGVnZW5kPWMoJ0xlYXN0IFNxdWFyZXMgUmVncmVzc2lvbiBMaW5lJywgJzk1JSBDb25maWRlbmNlIEludGVydmFsIEJhbmRzJywgJzk1JSBQcmVkaWN0aW9uIEludGVydmFsIEJhbmRzJyksIGNvbD1jKCdyZWQnLCBjaS5jb2wsIHBpLmNvbCksIGx0eT0xKQogIAogIHByaW50KFByZWRpY3REZW5zaXR5TGVhc3RTcXVhcmVzKDM4LjYpKSAgIyAzOC42IGlzIHRoZSBhdmVyYWdlIGdhaW4gZm9yIDAuNTA4IGRlbnNpdHkKICBwcmludChEZW5zaXR5Q2koMzguNikpCiAgcHJpbnQoRGVuc2l0eVBpKDM4LjYpKQogIAogIHByaW50KFByZWRpY3REZW5zaXR5TGVhc3RTcXVhcmVzKDQyNi43KSkgICMgNDI2LjcgaXMgdGhlIGF2ZXJhZ2UgZ2FpbiBmb3IgMC4wMDEgZGVuc2l0eQogIHByaW50KERlbnNpdHlDaSg0MjYuNykpCiAgcHJpbnQoRGVuc2l0eVBpKDQyNi43KSkKfQpgYGAKCgojIyBBZGRpdGlvbmFsIFNjZW5hcmlvOiBUZW1wZXJhdHVyZSwgRE9ZLCBhbmQgTGF0aXR1ZGUuClVzZSB0aGUgYWRkaXRpb25hbCBkYXRhc2V0IHRvIGNvbnN0cnVjdCBhIG1vZGVsIGZpdHRpbmcgdGVtcGVyYXR1cmUgd2l0aCBET1ksIGxhdGl0dWRlLCBhbmQgb3RoZXIgcmVhc29uYWJsZSBmZWF0dXJlcy4gVHJ5IHNrZXRjaGluZyB0aGUgbGVhc3Qgc3F1YXJlcyBsaW5lIG9uIGEgc2NhdHRlciBwbG90LiBXZSBhaW0gdG8gaW52ZXN0aWdhdGUgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRlbXBlcmF0dXJlIGFuZCB0aGUgRE9ZLCBhbmQgaXRzIGxhdGl0dWRlLgpgYGB7cn0KIyBDaGVjayB0aGUgY29ycmVsYXRpb24KZGF0YSA8LSByZWFkLmNzdignRnVsbCBSZXNvbHV0aW9uIERhdGEvNjQ1MDY0MjAuY3N2JywgaGVhZGVyPVRSVUUpCmRhdGEgPC0gZGF0YVssYygnSG91cicsJ0RPWScsJ1BPU19ET1knLCdMYXQnLCdMb24nLCdUcycsJ0JQJyldCgojIERyb3AgdGhlIGV4dHJlbWUgb3V0bGllciBjYXNlCiNkYXRhIDwtIGRhdGFbd2hpY2goZGF0YSRUcz4tMjAwKSxdCmRhdGFfbWF0cml4IDwtIGFzLm1hdHJpeChkYXRhKQoKIyBDb3JyZWxhdGlvbiBNYXRyaXgKY29ycl9tYXRyaXggPC0gY29yKGRhdGFfbWF0cml4KQpjb3JyX21hdHJpeApgYGAKCmBgYHtyfQojIEdyb3VwIGJ5IERPWSBhbmQgYXZlcmFnZSByZXBsaWNhdGVkIG1lYXN1cmVtZW50cwpkYXRhJERPWSA8LSByb3VuZChkYXRhJERPWSwwKQpkYXRhLmF2ZyA9IGFnZ3JlZ2F0ZShsaXN0KGRhdGE9ZGF0YVssYygnVHMnLCdMYXQnKV0pLCBieT1saXN0KERPWT1kYXRhJERPWSksIEZVTj1tZWFuKQoKIyBsZWFzdCBzcXVhcmVzIGxpbmUKZ2dwbG90KGRhdGEuYXZnLGFlcyh4PWRhdGEuYXZnJERPWSwgeT1kYXRhLmF2ZyRkYXRhLlRzKSkgKyAKICBnZW9tX3BvaW50KGNvbG9yPScjMjk4MEI5Jywgc2l6ZSA9IDQpICsgCiAgZ2VvbV9zbW9vdGgobWV0aG9kPWxtLCBjb2xvcj0nIzJDM0U1MCcpICtnZ3RpdGxlKGxhYmVsID0iTGVhc3QgU3F1YXJlcyBSZWdyZXNzaW9uIExpbmUiKSArIHhsYWIoIkRheSBPZiBZZWFyIikgKwogIHlsYWIoIlRlbXBlcmF0dXJlIikKZml0MTwtbG0oZm9ybXVsYSA9IGRhdGEuVHMgfiBET1ksIGRhdGEgPSBkYXRhLmF2ZykKc3VtbWFyeShmaXQxKQoKZ2dwbG90KGRhdGEuYXZnLGFlcyh4PWRhdGEuYXZnJGRhdGEuTGF0LCB5PWRhdGEuYXZnJGRhdGEuVHMpKSArIAogIGdlb21fcG9pbnQoY29sb3I9JyMyOTgwQjknLCBzaXplID0gNCkgKyAKICBnZW9tX3Ntb290aChtZXRob2Q9bG0sIGNvbG9yPScjMkMzRTUwJykgK2dndGl0bGUobGFiZWwgPSJMZWFzdCBTcXVhcmVzIFJlZ3Jlc3Npb24gTGluZSIpKyB4bGFiKCJMYXR0aXR1ZGUiKSArCiAgeWxhYigiVGVtcGVyYXR1cmUiKQpmaXQyPC1sbShmb3JtdWxhID0gZGF0YS5UcyB+IGRhdGEuTGF0LCBkYXRhID0gZGF0YS5hdmcpCnN1bW1hcnkoZml0MikKCiMgUG9seW5vbWlhbCBSZWdyZXNzaW9uIExpbmUKZml0MzwtbG0oZm9ybXVsYSA9IGRhdGEuVHMgfiBET1kgKyBkYXRhLkxhdCwgZGF0YSA9IGRhdGEuYXZnKQpzdW1tYXJ5KGZpdDMpCgpxcW5vcm0oZml0MiRyZXNpZHVhbHMsIG1haW49cGFzdGUoJ05vcm1hbCBRLVEgUGxvdCB3aXRoJywgdGl0bGUucmVzaWR1YWxzMSksIGNleC5tYWluPTEpCnFxbGluZShmaXQyJHJlc2lkdWFscywgY29sPSdyZWQnKQoKdGl0bGUucmVzaWR1YWxzMSA8LSAnUmVzaWR1YWxzIG9mIExlYXN0IFNxdWFyZSBSZWdyZXNzaW9uIExpbmUnCnBsb3QoZml0MiRyZXNpZHVhbHMsIG1haW49dGl0bGUucmVzaWR1YWxzMSwgeWxhYiA9ICJTdGFuZGFyZGl6ZWQgUmVzaWR1YWxzIikKYWJsaW5lKDAsIDAsIGNvbD0ncmVkJykKYGBg